﻿using ImageK.Process;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Plugin;

namespace ImageK.Util
{
    /** DICOM utilities */
    public class DicomTools
    {
        private static int MAX_DIGITS = 5;
        private static String[] sliceLabels;

        /** Sorts a DICOM stack by image number. */
        public static ImageStack sort(ImageStack stack)
        {
            if (IJ.debugMode) IJ.log("Sorting by DICOM image number");
            if (stack.size()==1) return stack;
            String[] strings = getSortStrings(stack, "0020,0013");
            if (strings==null) return stack;
            StringSorter.sort(strings);
            ImageStack stack2 = null;
            if (stack.isVirtual())
                stack2 = ((VirtualStack)stack).sortDicom(strings, sliceLabels, MAX_DIGITS);
            else
                stack2 = sortStack(stack, strings);
            return stack2!=null ? stack2 : stack;
        }

        private static ImageStack sortStack(ImageStack stack, String[] strings)
        {
            ImageProcessor ip = stack.getProcessor(1);
            ImageStack stack2 = new ImageStack(ip.getWidth(), ip.getHeight(), ip.getColorModel());
            for (int i = 0; i<stack.size(); i++)
            {
                int slice = (int)Tools.parseDouble(strings[i].Substring(strings[i].Length-MAX_DIGITS), 0.0);
                if (slice==0) return null;
                stack2.addSlice(sliceLabels[slice-1], stack.getPixels(slice));
            }
            stack2.update(stack.getProcessor(1));
            return stack2;
        }

        private static String[] getSortStrings(ImageStack stack, String tag)
        {
            double series = getSeriesNumber(getSliceLabel(stack, 1));
            int n = stack.size();
            bool checkRescaleSlope = (stack is VirtualStack)?((VirtualStack)stack).getBitDepth()==16:false;
            if (Prefs.ignoreRescaleSlope)
                checkRescaleSlope = false;
            bool showError = false;
            String[] values = new String[n];
            sliceLabels = new String[n];
            for (int i = 1; i<=n; i++)
            {
                String tags = getSliceLabel(stack, i);
                if (tags==null) return null;
                sliceLabels[i-1] = tags;
                double value = getNumericTag(tags, tag);
                if (Double.IsNaN(value))
                {
                    if (IJ.debugMode) IJ.log("  "+tag+"  tag missing in slice "+i);
                    if (showError) rescaleSlopeError(stack);
                    return null;
                }
                if (getSeriesNumber(tags)!=series)
                {
                    if (IJ.debugMode) IJ.log("  all slices must be part of the same series");
                    if (showError) rescaleSlopeError(stack);
                    return null;
                }
                values[i-1] = toString(value, MAX_DIGITS) + toString(i, MAX_DIGITS);
                if (checkRescaleSlope)
                {
                    double rescaleSlope = getNumericTag(tags, "0028,1053");
                    if (rescaleSlope!=1.0)
                        showError = true;
                }
            }
            if (showError) rescaleSlopeError(stack);
            return values;
        }

        private static void rescaleSlopeError(ImageStack stack)
        {
            ((VirtualStack)stack).setBitDepth(32);
        }

        private static String toString(double value, int width)
        {
            String s = "       " + IJ.d2s(value, 0);
            return s.Substring(s.Length-MAX_DIGITS);
        }

        private static String getSliceLabel(ImageStack stack, int n)
        {
            String info = stack.getSliceLabel(n);
            if ((info==null || info.Length<100) && stack.isVirtual())
            {
                String dir = ((VirtualStack)stack).getDirectory();
                String name = ((VirtualStack)stack).getFileName(n);
                DICOM reader = new DICOM();
                info = reader.getInfo(dir+name);
                if (info!=null)
                    info = name + "\n" + info;
            }
            return info;
        }

        /** Calculates the voxel depth of the specified DICOM stack based 
            on the distance between the first and last slices. */
        public static double getVoxelDepth(ImageStack stack)
        {
            if (stack.isVirtual()) stack.getProcessor(1);
            String pos0 = getTag(stack.getSliceLabel(1), "0020,0032");
            String posn = null;
            double voxelDepth = -1.0;
            if (pos0!=null)
            {
                String[] xyz = pos0.Split("\\\\");
                if (xyz.Length!=3) return voxelDepth;
                double z0 = double.Parse(xyz[2]);
                if (stack.isVirtual()) stack.getProcessor(stack.size());
                posn = getTag(stack.getSliceLabel(stack.size()), "0020,0032");
                if (posn==null) return voxelDepth;
                xyz = posn.Split("\\\\");
                if (xyz.Length!=3) return voxelDepth;
                double zn = Double.Parse(xyz[2]);
                voxelDepth = Math.Abs((zn - z0) / (stack.size() - 1));
            }
            if (IJ.debugMode) IJ.log("DicomTools.getVoxelDepth: "+voxelDepth+"  "+pos0+"  "+posn);
            return voxelDepth;
        }

        /** Returns the value (as a string) of the specified DICOM tag id (in the form "0018,0050")
            of the specified image or stack slice. Returns null if the tag id is not found. */
        public static String getTag(ImagePlus imp, String id)
        {
            String metadata = null;
            if (imp.getStackSize()>1)
            {
                ImageStack stack = imp.getStack();
                String label = stack.getSliceLabel(imp.getCurrentSlice());
                if (label!=null && label.IndexOf('\n')>0) metadata = label;
            }
            if (metadata==null)
                metadata = (String)imp.getProperty("Info");
            return getTag(metadata, id);
        }

        /** Returns the name of the specified DICOM tag id. */
        public static string getTagName(String id)
        {
            return DICOM.getTagName(id);
        }

        private static double getSeriesNumber(String tags)
        {
            double series = getNumericTag(tags, "0020,0011");
            if (double.IsNaN(series)) series = 0;
            return series;
        }

        private static double getNumericTag(String hdr, String tag)
        {
            String value = getTag(hdr, tag);
            if (value==null) return Double.NaN;
            int index3 = value.IndexOf("\\");
            if (index3>0)
                value = value.Substring(0, index3);
            return Tools.parseDouble(value);
        }

        private static string getTag(String hdr, String tag)
        {
            if (hdr==null) return null;
            int index1 = hdr.IndexOf(tag);
            if (index1==-1) return null;
            //IJ.log(hdr.charAt(index1+11)+"   "+hdr.substring(index1,index1+20));
            if (hdr[index1+11]=='>')
            {
                // ignore tags in sequences
                index1 = hdr.IndexOf(tag, index1+10);
                if (index1==-1) return null;
            }
            index1 = hdr.IndexOf(":", index1);
            if (index1==-1) return null;
            int index2 = hdr.IndexOf("\n", index1);
            string value = hdr.Substring(index1+1, index2);
            return value;
        }

    }
}
